Flutter webview_flutter pigeon 通信桥实现原理
webview_flutter 同时支持多个平台(Android、iOS、Web),对各平台下的原生代码进行封装,导出一套统一的 Dart 接口,其中使用到由 Flutter 团队开发的 pigeon 框架。
pigeon 框架基于代码生成技术,专门用于 Flutter 跨端场景下,多端原生实现与 Flutter 的高效打通。对 pigeon 感兴趣的同学,请自行了解。本文中假设已有 pigeon 开发经验。
回到 webview_flutter,为了后续研究原生侧实现,需要首先梳理清楚 Dart 与原生侧的通信桥,这也是本文的主题。
webview_flutter_platform_interface
声明了各平台通用接口,这个接口允许 webview_flutter
插件的平台特定实现,以及插件本身,确保它们支持相同的接口。
如果要实现一个新的针对 webview_flutter
的平台特定实现,你需要扩展 WebviewPlatform,并提供一个执行平台特定行为的实现。当你注册你的插件时,通过调用 WebviewPlatform.instance = MyPlatformWebview()
来设置默认的 WebviewPlatform
。
WebViewPlatform
对于要实现 WebView 的平台,都应当实现该接口。它定义了一些核心方法:
createPlatformCookieManager
: 创建 Cookie 管理器createPlatformNavigationDelegate
: 创建导航代理类createPlatformWebViewController
: 创建 WebView 控制器createPlatformWebViewWidget
: 平台 WebView 组件
这四个方法分别对应于 webview_flutter 中的四个核心类,将在后文中分别介绍。这四个类,承接了 webview_flutter 中的大多数功能,因此,这四个类,也是分析这个库的“四大入口”。
各平台实现如下:
- Android 平台:
AndroidWebViewPlatform
WebViewAndroidPlatformController
:旧的,已入 legacy
- iOS 平台:
WebKitWebViewPlatform
- Web 平台:
WebWebViewPlatform
WebKitWebViewPlatformController
:旧的,已入 legacy
关于 WebViewPlatform 的具体使用,在后续小节中,我以 Android 为例进行了分析,进行梳理。现在,让我们继续,把 webview_flutter_platform_interface 里面的几个接口,都看一看。
PlatformWebViewCookieManager
这个类定义了一个抽象的 cookie 管理器,它可以被不同的平台(如 Android、iOS)扩展,以提供平台特定的 cookie 管理功能。
该类提供了两个核心方法:
setCookie
:设置 CookieclearCookies
:清空 Cookie
PlatformNavigationDelegate
这个类是一个抽象的代理,它定义了一些方法来处理在 webview 上发生的导航事件,如页面加载、页面跳转等。具体的行为需要由实现这个接口的类来提供。
该类核心方法:
setOnNavigationRequest
: 导航请求回调setOnPageStarted
: 页面开始加载回调setOnPageFinished
: 页面加载完成回调setOnHttpError
: 加载过程中发生 HTTP 错误回调setOnProgress
: 加载进度回调setOnWebResourceError
: 当资源加载错误发回调setOnUrlChange
: URL 变化回调setOnHttpAuthRequest
: web view 请求认证回调
PlatformWebViewController
这个类定义了一个抽象的 web view 控制器,它可以被不同的平台(如 Android、iOS)扩展,以提供平台特定的 web view 控制功能。
该类中定义了可由开发者操作的 WebView 方法,比如跳转 url、前进后退、运行 JavaScript 等。
对于这个类,我在《PlatformWebViewController》这篇笔记中进行了专门介绍,可点击阅读。
PlatformWebViewWidget
这个类可以理解是 WebView 的工厂类,它有一个 build
方法,返回一个 Flutter Widget,即 Flutter 侧的 WebView(基于 PlatformView)。
AndroidWebViewPlatform
上面四个核心类分析完,我们回到 WebViewPlatform,这里以 Android 平台为例,分析 AndroidWebViewPlatform。
此时也来到了 webview_flutter_android 包下,AndroidWebViewPlatform 实现了 WebViewPlatform 的四大方法:
createPlatformWebViewController
:返回AndroidWebViewController
createPlatformNavigationDelegate
:返回AndroidNavigationDelegate
createPlatformWebViewWidget
:返回AndroidWebViewWidget
createPlatformCookieManager
:返回AndroidWebViewCookieManager
显然,返回的这四个类,是对我们上面介绍的那四个接口的继承实现。
在这 4 个 Android 开头实现类中,他们还是 Dart 类,但是,他们内部各自包含一些 Dart 成员类属性,这些成员类属性负责与原生侧的通信了。
Pigeon 呢?
说了这么多,都是些普通接口,怎么不见 Pigeon 身影呢?
下面,我们进入 Pigeon 部分。
android_webview.dart
在 webview_flutter_android 工程下,有一个 pigeons 包,里面有一个 android_webview.dart,这是 Pigeon 的接口声明,它本身不参与 Dart 编译,
该类映入眼帘的一段代码,足够说明它绝非等同之辈:
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/src/android_webview.g.dart',
dartTestOut: 'test/test_android_webview.g.dart',
dartOptions: //...,
javaOut:
'android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java',
javaOptions: JavaOptions(
package: 'io.flutter.plugins.webviewflutter',
className: 'GeneratedAndroidWebView',
copyrightHeader: //...
],
),
),
)
这是一段 Pigeon 的配置,告诉代码生成器,根据 android_webview.dart 中声明的接口,需要生成两份声明:
- 一份是 Dart 侧的:生成 android_webview.g.dart,里面包括枚举,Model 实体类,模块的 Dart 侧实现
- 一份是 Android 侧的:生成 GeneratedAndroidWebView.java,里面包括枚举,Model 实体类,模块的 Java 侧实现
其中:
- 枚举:Dart 侧和 Android 侧,虽然一份是 Dart 代码,一份是 Java 代码,但两者是对其的
- Model 实体类,Dart 侧和 Android 侧,虽然一份是 Dart 代码,一份是 Java 代码,两者不仅对其,而且还能序列化转换,一个 Dart Model 实例,可传到 Java 侧变为 Java 同名 Model 实例,反之亦然。
- 至于模块,则分为两种:
- 一种是实现在 Android 侧的,生成的 Java 代码用于注入实现,生成的 Dart 代码用于调用
- 另外一种是实现在 Dart 侧的,生成的 Dart 代码用于注入实现,生成的 Java 代码用于调用
如果你感到有些抽象或吃力,说明首先需要补充一下 pigeon 相关基础。
下面我们来看 android_webview.dart 中定义的 pigeon 通信桥。
ConsoleMessage
先来一个简单的,这是一个实体类,定义了一条命令行日志:
class ConsoleMessage {
late int lineNumber;
late String message;
late ConsoleMessageLevel level;
late String sourceId;
}
CookieManagerHostApi
@HostApi(dartHostTestHandler: 'TestCookieManagerHostApi')
abstract class CookieManagerHostApi {
/// Handles attaching `CookieManager.instance` to a native instance.
void attachInstance(int instanceIdentifier);
/// Handles Dart method `CookieManager.setCookie`.
void setCookie(int identifier, String url, String value);
/// Handles Dart method `CookieManager.removeAllCookies`.
@async
bool removeAllCookies(int identifier);
/// Handles Dart method `CookieManager.setAcceptThirdPartyCookies`.
void setAcceptThirdPartyCookies(
int identifier,
int webViewIdentifier,
bool accept,
);
}
这是一个 HostApi 类,表示这个模块实现在 Android 侧的,生成的 Java 代码用于注入实现,生成的 Dart 代码用于调用。
来到生成的 dart 文件 android_webview.g.dart 下,找到 Dart 侧生成后的 CookieManagerHostApi,起内部的 setCookie、removeAllCookies 方法都是 Channel 调用,调用 Java 侧对应实现,并将结果返回 Dart 侧。
我们看谁继承了生成后的 CookieManagerHostApi,CookieManagerHostApiImpl
类继承了 CookieManagerHostApi。
在 CookieManager
Dart 类中,将 CookieManagerHostApiImpl
作为类成员使用。
前面说到 AndroidWebViewPlatform 会创建 AndroidWebViewCookieManager
实例。 在 AndroidWebViewCookieManager
类中,使用 CookieManager
作为类成员。还记得 AndroidWebViewCookieManager
的父类吗?PlatformWebViewCookieManager
,四大组成部分之一。
至此,我们知道了,AndroidWebViewCookieManager
内部通过层层转发,最终通过 pigeon 转到 Native 侧实现。
WebViewHostApi
继续如法炮制,回到 pigeon 接口的 WebViewHostApi,该类定义了对 WebView 主动操作的一些方法。找到 Dart 侧实现的 android_webview.g.dart 下的 WebViewHostApi,看谁用到它。
找到 WebViewHostApiImpl 继承自 WebViewHostApi。再看谁用到 WebViewHostApiImpl。
来到 android_webview.dart(另一个)下的 WebView 类,以 WebViewHostApiImpl 作为成员。谁用到 WebView?
来到 AndroidWebViewController,它内部以 WebViewHostApiImpl 作为成员。
现在知道 AndroidWebViewController 是怎么控制 WebView 的了,它也通过内部层层转发,最终通过 pigeon 转到 Native 侧实现。
WebViewFlutterApi
最后再分析一个压轴的——WebViewFlutterApi,顾名思义,他是在 Dart 侧触发 Native 创建 WebView 的。尽管任务重,但是 WebViewFlutterApi 接口声明很简单:
@FlutterApi()
abstract class WebViewFlutterApi {
/// Create a new Dart instance and add it to the `InstanceManager`.
void create(int identifier);
}
简单明了,只有一个 create 接口。
但是,这里面有一个注意点,这个模块是 FlutterApi
,说明他的实现在 Flutter 侧,原生侧只是调用。
android_webview.g.dart 中会生成 WebViewFlutterApi 的注入类,并提供 setup 供 Dart 侧注入实现。实现的具体注入位于 AndroidWebViewFlutterApis
的 ensureSetUp 方法。传入的实现类是 WebViewFlutterApiImpl,该类继承自 WebViewFlutterApi。我们看 create 的实现:
@override
void create(int identifier) {
instanceManager.addHostCreatedInstance(WebView.detached(), identifier);
}
这什么意思?调用这个方法时,Java 侧的 WebView 已经创建好了,将 instanceManager 中,对这个 Java 实例的唯一标识通过 pegion 传到 Dart 侧,Dart 侧将其保存到 Dart 这边的 instanceManager 中,并且将 identifier(俗称句柄)与 Dart 侧的 WebView 相关联。
再看看 Java 侧这个接口是什么时候调用的。位于 WebViewFlutterApiImpl,其 create 调用:
public void create(@NonNull WebView instance, @NonNull WebViewFlutterApi.Reply<Void> callback) {
if (!instanceManager.containsInstance(instance)) {
api.create(instanceManager.addHostCreatedInstance(instance), callback);
}
}
可以看到,现在 Java 侧的 instanceManager 存一下,然后再调往 Dart 侧的通信。
WebViewFlutterApiImpl 的 create 又是谁调用的呢?来到了 WebViewPlatformView 类,他是 Java 侧的 Android WebView 实现类。WebViewFlutterApiImpl 是 WebViewPlatformView 的内部成员。
在 WebViewPlatformView 的构造方法中,同时会创建 WebViewFlutterApiImpl 实例:
@VisibleForTesting
WebViewPlatformView(
@NonNull Context context,
@NonNull BinaryMessenger binaryMessenger,
@NonNull InstanceManager instanceManager,
@NonNull AndroidSdkChecker sdkChecker) {
super(context);
currentWebViewClient = new WebViewClient();
currentWebChromeClient = new WebChromeClientHostApiImpl.SecureWebChromeClient();
// 创建 WebViewFlutterApiImpl 实例
api = new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
this.sdkChecker = sdkChecker;
setWebViewClient(currentWebViewClient);
setWebChromeClient(currentWebChromeClient);
}
另一处用到 WebViewFlutterApiImpl 的是 WebChromeClientFlutterApiImpl 和 WebViewClientFlutterApiImpl,他们是与 Android WebView 配合工作的 Java Android 类,在这两个类的事件相应方法中,都会先调用 create 方法,确保在 Dart 侧创建有映射实例。
举一例,如下:
/** Passes arguments from {@link WebViewClient#onPageStarted} to Dart. */
public void onPageStarted(
@NonNull WebViewClient webViewClient,
@NonNull WebView webView,
@NonNull String urlArg,
@NonNull Reply<Void> callback) {
webViewFlutterApi.create(webView, reply -> {});
final Long webViewIdentifier =
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
onPageStarted(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
}
该文分析的逻辑有待完善 #TODO
这篇文章是我初学时,从细节入手,边学边梳理的。
在我学会之后,建立起全局认识后,有了更好的叙事结构。但是我的精力有限,短时间内也许没有精力完善本文。我想,如果你也在对 webview_flutter 进行深入研究,这些细节仍然是有价值的。
也许补充一些图片会更好一些,同样也是精力有限。在 GeneratedAndroidWebView.java 中会生成 WebViewFlutterApi 同名 Java 类,他的 create 内部是一个 Channel 调用,调用上面的 Dart create。
现在看 create 是哪里调用的?
本文作者:Maeiee
本文链接:Flutter webview_flutter pigeon 通信桥实现原理
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!